﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;


/**
 * M3Gで定義されたデータ型と内部実装の型
 *   Byte        --> byte
 *   Short,Half  --> ushort
 *   Fixed       --> uint
 *   Float       --> float
 *   保存はBitConverterを使ってbyte[]に直して保存する。
 * */


namespace M3G {
    public enum ArrayType {
        Byte  = 1,
        Short = 2,
        Fixed = 3,
        Float = 4,
        Half  = 5
    }

    public static class ArrayTypeExtensions {
        public static int ToByteCount (this ArrayType type) {
            switch (type) {
                case ArrayType.Byte: return 1;
                case ArrayType.Short: return 2;
                case ArrayType.Half: return 2;
                case ArrayType.Fixed: return 4;
                case ArrayType.Float: return 4;
                default: return 0;
            }
        }
    }


    public class VertexArray : Object3D {
        #region Constant
        #endregion

        #region Field
        int       vertexCount;
        int       componentCount;
        ArrayType componentType;
        int       bpc;
        byte[]    byteArray;
        #endregion



        #region Method
        public VertexArray (int numVertices, int numComponents, ArrayType componentType) {
            if (componentType < ArrayType.Byte || componentType > ArrayType.Half) {
                throw new ArgumentException ("Component type is invalid, type=" + componentType);
            }
            if (numVertices < 1 || numVertices > 65535) {
                throw new ArgumentException ("Number of vertices is invalid, num=" + numVertices);
            }
            if (numComponents < 1 || numComponents > 4) {
                throw new ArgumentException ("Number of components is invalid, num=" + numComponents);
            }

            this.componentType = componentType;
            this.vertexCount = numVertices;
            this.componentCount = numComponents;
            this.bpc = componentType.ToByteCount();
            this.byteArray = new byte[vertexCount * componentCount * Bpc];
        }

        protected VertexArray (VertexArray vertexArray)
            : base (vertexArray) {
            this.vertexCount = vertexArray.vertexCount;
            this.componentCount = vertexArray.componentCount;
            this.componentType = vertexArray.componentType;
            this.bpc = vertexArray.bpc;
            this.byteArray = (byte[])vertexArray.byteArray.Clone ();
        }

        public int ComponentCount {
            get { return componentCount; }
        }

        public ArrayType ComponentType {
            get { return componentType; }
        }

        public int VertexCount {
            get { return vertexCount; }
        }

        public int Bpc {
            get { return bpc; }
        }

        public void Get (int firstVertex, int numVertices, byte[] dst) {
            if (dst == null) {
                throw new ArgumentNullException ("Destination is null");
            }
            if (componentType != ArrayType.Byte) {
                throw new ArgumentException ("Component type is invalid, type=" + componentType);
            }
            if (numVertices < 0) {
                throw new ArgumentException ("Number of vertices is invalid, num=" + numVertices);
            }
            if (dst.Length < numVertices * componentCount) {
                throw new ArgumentOutOfRangeException ("Destination is less than required, len=" + dst.Length + "<" + numVertices * componentCount);
            }
            if (firstVertex < 0 || firstVertex + numVertices > vertexCount) {
                throw new ArgumentOutOfRangeException ("Vertex index reaches upper bound, index=" + firstVertex + numVertices + ">" + vertexCount);
            }

            for (var i=0; i < numVertices * componentCount; i++) {
                dst[i] = byteArray[i + firstVertex * componentCount];
            }
        }

        public void Get (int firstVertex, int numVertices, sbyte[] dst) {
            if (dst == null) {
                throw new ArgumentNullException ("Destination is null");
            }
            if (componentType != ArrayType.Byte) {
                throw new ArgumentException ("Component type is invalid, type=" + componentType);
            }
            if (numVertices < 0) {
                throw new ArgumentException ("Number of vertices is invalid, num=" + numVertices);
            }
            if (dst.Length < numVertices * componentCount) {
                throw new ArgumentOutOfRangeException ("Destination is less than required, len=" + dst.Length + "<" + numVertices * componentCount);
            }
            if (firstVertex < 0 || firstVertex + numVertices > vertexCount) {
                throw new ArgumentOutOfRangeException ("Vertex index reaches upper bound, index=" + firstVertex + numVertices + ">" + vertexCount);
            }

            for (var i=0; i < numVertices * componentCount; i++) {
                dst[i] = (sbyte)byteArray[i + firstVertex * componentCount];
            }
        }


        public void Get (int firstVertex, int numVertices, short[] dst) {
            if (dst == null) {
                throw new ArgumentNullException ("Destination is null");
            }
            if (componentType != ArrayType.Short && componentType != ArrayType.Half) {
                throw new ArgumentException ("Component type is invalid, type=" + componentType);
            }
            if (numVertices < 0) {
                throw new ArgumentException ("Number of vertices is invalid, num=" + numVertices);
            }
            if (dst.Length < numVertices * componentCount) {
                throw new ArgumentOutOfRangeException ("Destination is less than required, len=" + dst.Length + "<" + numVertices * componentCount);
            }
            if (firstVertex < 0 || firstVertex + numVertices > vertexCount) {
                throw new ArgumentOutOfRangeException ("Vertex index reaches upper bound, index=" + firstVertex + numVertices + ">" + vertexCount);
            }

            for (var i=0; i < numVertices * componentCount; i++) {
                dst[i] = BitConverter.ToInt16 (byteArray, (firstVertex * componentCount + i) * 2);
            }
        }

        public void Get (int firstVertex, int numVertices, ushort[] dst) {
            if (dst == null) {
                throw new ArgumentNullException ("Destination is null");
            }
            if (componentType != ArrayType.Short && componentType != ArrayType.Half) {
                throw new ArgumentException ("Component type is invalid, type=" + componentType);
            }
            if (numVertices < 0) {
                throw new ArgumentException ("Number of vertices is invalid, num=" + numVertices);
            }
            if (dst.Length < numVertices * componentCount) {
                throw new ArgumentOutOfRangeException ("Destination is less than required, len=" + dst.Length + "<" + numVertices * componentCount);
            }
            if (firstVertex < 0 || firstVertex + numVertices > vertexCount) {
                throw new ArgumentOutOfRangeException ("Vertex index reaches upper bound, index=" + firstVertex + numVertices + ">" + vertexCount);
            }

            for (var i=0; i < numVertices * componentCount; i++) {
                dst[i] = BitConverter.ToUInt16 (byteArray, (firstVertex * componentCount + i) * 2);
            }
        }

        public void Get (int firstVertex, int numVertices, int[] dst) {
            if (dst == null) {
                throw new ArgumentNullException ("Destination is null");
            }
            if (componentType != ArrayType.Fixed) {
                throw new ArgumentException ("Component type is invalid, type=" + componentType);
            }
            if (numVertices < 0) {
                throw new ArgumentException ("Number of vertices is invalid, num=" + numVertices);
            }
            if (dst.Length < numVertices * componentCount) {
                throw new ArgumentOutOfRangeException ("Destination is less than required, len=" + dst.Length + "<" + numVertices * componentCount);
            }
            if (firstVertex < 0 || firstVertex + numVertices > vertexCount) {
                throw new ArgumentOutOfRangeException ("Vertex index reaches upper bound, index=" + firstVertex + numVertices + ">" + vertexCount);
            }

            for (var i=0; i < numVertices * componentCount; i++) {
                dst[i] = BitConverter.ToInt32 (byteArray, (firstVertex * componentCount + i) * 4);
            }
        }

        public void Get (int firstVertex, int numVertices, uint[] dst) {
            if (dst == null) {
                throw new ArgumentNullException ("Destination is null");
            }
            if (componentType != ArrayType.Fixed) {
                throw new ArgumentException ("Component type is invalid, type=" + componentType);
            }
            if (numVertices < 0) {
                throw new ArgumentException ("Number of vertices is invalid, num=" + numVertices);
            }
            if (dst.Length < numVertices * componentCount) {
                throw new ArgumentOutOfRangeException ("Destination is less than required, len=" + dst.Length + "<" + numVertices * componentCount);
            }
            if (firstVertex < 0 || firstVertex + numVertices > vertexCount) {
                throw new ArgumentOutOfRangeException ("Vertex index reaches upper bound, index=" + firstVertex + numVertices + ">" + vertexCount);
            }

            for (var i=0; i < numVertices * componentCount; i++) {
                dst[i] = BitConverter.ToUInt32 (byteArray, (firstVertex * componentCount + i) * 4);
            }
        }

        public void Get (int firstVertex, int numVertices, float[] dst) {
            if (dst == null) {
                throw new ArgumentNullException ("Destination is null");
            }
            if (numVertices < 0) {
                throw new ArgumentException ("Number of vertices is invalid, num=" + numVertices);
            }
            if (dst.Length < numVertices * componentCount) {
                throw new ArgumentOutOfRangeException ("Destination is less than required, len=" + dst.Length + "<" + numVertices * componentCount);
            }
            if (firstVertex < 0 || firstVertex + numVertices > vertexCount) {
                throw new ArgumentOutOfRangeException ("Vertex index reaches upper bound, index=" + (firstVertex + numVertices) + ">" + vertexCount);
            }


            for (var i=0; i < numVertices * componentCount; i++) {
                switch (componentType) {
                    case ArrayType.Byte: dst[i] = byteArray[firstVertex * componentCount + i]; break;
                    case ArrayType.Short: dst[i] = BitConverter.ToUInt16 (byteArray, (firstVertex * componentCount + i) * 2); break;
                    case ArrayType.Half: dst[i] = Half2Float (BitConverter.ToUInt16 (byteArray, (firstVertex * componentCount + i) * 2)); break;
                    case ArrayType.Fixed: dst[i] = Fixed2Float (BitConverter.ToUInt32 (byteArray, (firstVertex * componentCount + i) * 4)); break;
                    case ArrayType.Float: dst[i] = BitConverter.ToSingle (byteArray, (firstVertex * componentCount + i) * 4); break;
                }
            }
        }

        /**
         * float形式で値を取得した後Scale,Biasをかける。M3G非標準
         * */
        public void Get (int firstVertex, int numVertices, float[] dst, float scale, float[] bias) {
            if (bias.Length < componentCount) {
                throw new ArithmeticException ("Length of bias is less than required, len=" + bias.Length + ",req=" + componentCount);
            }

            Get (firstVertex, numVertices, dst);

            for (var i=0; i < numVertices * componentCount; i++) {
                dst[i] = dst[i] * scale + bias[i % componentCount];
            }
        }

        float Fixed2Float (uint value) {
            return value / 65536.0f;
        }


        float Half2Float (ushort value) {
            return SlimMath.HalfUtilities.Unpack (value);
        }

        ushort Float2Half (float value) {
            return SlimMath.HalfUtilities.Pack (value);
        }


        public void Set (int firstVertex, int numVertices, sbyte[] src) {
            if (src == null) {
                throw new ArgumentNullException ("Source is null");
            }
            if (componentType != ArrayType.Byte) {
                throw new InvalidOperationException ("Data type is miss match, type=" + componentType);
            }
            if (numVertices < 0) {
                throw new ArgumentException ("Number of vertices is invalid, num=" + numVertices);
            }
            var required = numVertices * componentCount;
            if (src.Length < required) {
                throw new ArgumentException ("Source is less than required, len=" + src.Length + ",req=" + required);
            }
            if (firstVertex < 0 || firstVertex + numVertices > vertexCount) {
                throw new IndexOutOfRangeException ("Total writing vertex count is out of range, index=" + (firstVertex + numVertices));
            }

            for (var i=0; i < required; i++) {
                byteArray[i + firstVertex * componentCount] = (byte)src[i];
            }
        }

        public void Set (int firstVertex, int numVertices, byte[] src) {
            if (src == null) {
                throw new ArgumentNullException ("Source is null");
            }
            if (componentType != ArrayType.Byte) {
                throw new InvalidOperationException ("Data type is miss match, type=" + componentType);
            }
            if (numVertices < 0) {
                throw new ArgumentException ("Number of vertices is invalid, num=" + numVertices);
            }
            var required = numVertices * componentCount;
            if (src.Length < required) {
                throw new ArgumentException ("Source is less than required, len=" + src.Length + ",req=" + required);
            }
            if (firstVertex < 0 || firstVertex + numVertices > vertexCount) {
                throw new IndexOutOfRangeException ("Total writing vertex count is out of range, index=" + (firstVertex + numVertices));
            }

            for (var i=0; i < required; i++) {
                byteArray[i + firstVertex * componentCount] = src[i];
            }
        }

        public void Set (int firstVertex, int numVertices, short[] src) {
            if (src == null) {
                throw new ArgumentNullException ("Source is null");
            }
            if (!(componentType == ArrayType.Short || componentType == ArrayType.Half)) {
                throw new InvalidOperationException ("Data type is miss match, type=" + componentType);
            }
            if (numVertices < 0) {
                throw new ArgumentException ("Number of vertices is invalid, num=" + numVertices);
            }
            var required = numVertices * componentCount;
            if (src.Length < required) {
                throw new ArgumentException ("Source is less than required, len=" + src.Length + ",req=" + required);
            }
            if (firstVertex < 0 || firstVertex + numVertices > vertexCount) {
                throw new IndexOutOfRangeException ("Total writing vertex count is out of range, index=" + (firstVertex + numVertices));
            }

            for (var i=0; i < numVertices * componentCount; i++) {
                var value = BitConverter.GetBytes (src[i]);
                value.CopyTo (this.byteArray, (firstVertex * componentCount + i) * 2);
            }
        }

        public void Set (int firstVertex, int numVertices, ushort[] src) {
            if (src == null) {
                throw new ArgumentNullException ("Source is null");
            }
            if (componentType != ArrayType.Short && componentType != ArrayType.Half) {
                throw new InvalidOperationException ("Data type is miss match, type=" + componentType);
            }
            if (numVertices < 0) {
                throw new ArgumentException ("Number of vertices is invalid, num=" + numVertices);
            }
            var required = numVertices * componentCount;
            if (src.Length < required) {
                throw new ArgumentException ("Source is less than required, len=" + src.Length + ",req=" + required);
            }
            if (firstVertex < 0 || firstVertex + numVertices > vertexCount) {
                throw new IndexOutOfRangeException ("Total writing vertex count is out of range, index=" + (firstVertex + numVertices));
            }

            for (var i=0; i < numVertices * componentCount; i++) {
                var value = BitConverter.GetBytes (src[i]);
                value.CopyTo (this.byteArray, (firstVertex * componentCount + i) * 2);
            }
        }



        public void Set (int firstVertex, int numVertices, int[] src) {
            if (src == null) {
                throw new ArgumentNullException ("Source is null");
            }
            if (componentType != ArrayType.Fixed) {
                throw new InvalidOperationException ("Data type is miss match, type=" + componentType);
            }
            if (numVertices < 0) {
                throw new ArgumentException ("Number of vertices is invalid, num=" + numVertices);
            }
            if (src.Length < numVertices * componentCount) {
                throw new ArgumentException ("Source is less than required, len=" + src.Length);
            }
            if (firstVertex < 0 || firstVertex + numVertices > vertexCount) {
                throw new IndexOutOfRangeException ("Total writing vertex count is out of range, index=" + (firstVertex + numVertices));
            }

            for (var i=0; i < numVertices * componentCount; i++) {
                var value = BitConverter.GetBytes (src[i]);
                value.CopyTo (this.byteArray, (firstVertex * componentCount + i) * 4);
            }
        }

        public void Set (int firstVertex, int numVertices, uint[] src) {
            if (src == null) {
                throw new ArgumentNullException ("Source is null");
            }
            if (componentType != ArrayType.Fixed) {
                throw new InvalidOperationException ("Data type is miss match, type=" + componentType);
            }
            if (numVertices < 0) {
                throw new ArgumentException ("Number of vertices is invalid, num=" + numVertices);
            }
            if (src.Length < numVertices * componentCount) {
                throw new ArgumentException ("Source is less than required, len=" + src.Length);
            }
            if (firstVertex < 0 || firstVertex + numVertices > vertexCount) {
                throw new IndexOutOfRangeException ("Total writing vertex count is out of range, index=" + (firstVertex + numVertices));
            }

            for (var i=0; i < numVertices * componentCount; i++) {
                var value = BitConverter.GetBytes (src[i]);
                value.CopyTo (this.byteArray, (firstVertex * componentCount + i) * 4);
            }
        }

        public void Set (int firstVertex, int numVertices, float[] src) {
            if (src == null) {
                throw new ArgumentNullException ("Source is null");
            }
            if (!(componentType == ArrayType.Float || componentType == ArrayType.Half)) {
                throw new InvalidOperationException ("Data type is miss match, type=" + componentType);
            }
            if (numVertices < 0) {
                throw new ArgumentException ("Number of vertices is invalid, num=" + numVertices);
            }
            var required = numVertices * componentCount;
            if (src.Length < required) {
                throw new ArgumentException ("Source is less than required, len=" + src.Length + ",req=" + required);
            }
            if (firstVertex < 0 || firstVertex + numVertices > vertexCount) {
                throw new IndexOutOfRangeException ("Total writing vertex count is out of range, index=" + (firstVertex + numVertices));
            }

            for (var i=0; i < numVertices * componentCount; i++) {
                if (componentType == ArrayType.Float) {
                    var value = BitConverter.GetBytes (src[i]);
                    value.CopyTo (this.byteArray, (firstVertex * componentCount + i) * 4);
                }
                if (componentType == ArrayType.Half) {
                    var value = BitConverter.GetBytes (Float2Half (src[i]));
                    value.CopyTo (this.byteArray, (firstVertex * componentCount + i) * 2);
                }
            }
        }

        public void Set (int firstVertex, int numVertices, VertexArray src) {
            if (src == null) {
                throw new ArgumentNullException ("Source is null");
            }
            if (src.componentCount != this.componentCount) {
                throw new ArgumentException ("Component count is invalid, src=" + src.componentCount + ",this=" + this.componentCount);
            }
            if (src.componentType != this.componentType) {
                throw new ArgumentException ("Component type is invalid, src=" + src.componentType + ",this=" + this.componentType);
            }
            if (src.vertexCount != this.vertexCount) {
                throw new ArgumentException ("Vertex count is invalid, src=" + src.vertexCount + ",this=" + this.vertexCount);
            }
            if (numVertices < 0) {
                throw new ArgumentException ("Number of vertices is invalid, num=" + numVertices);
            }
            if (firstVertex < 0 || firstVertex + numVertices > this.vertexCount) {
                throw new ArgumentOutOfRangeException ("Total writing vertex count is out of range, index=" + firstVertex + numVertices);
            }

            Array.Copy (src.byteArray, 0,
                        this.byteArray, firstVertex * componentCount * bpc,
                        numVertices * componentCount * bpc);
        }

        public override Object3D Duplicate () {
            return new VertexArray (this);
        }

        public byte[] ByteArray {
            get { return byteArray; }
        }
        #endregion


    }
}
